package org.crown.project.api.config.filter;


import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.crown.common.utils.Md5Utils;
import org.crown.common.utils.StringUtils;
import org.crown.framework.responses.ResponseVo;
import org.crown.framework.utils.ResponseUtils;
import org.crown.project.api.BaseApi;
import org.crown.project.api.utils.AesUtil;
import org.crown.project.api.utils.SignUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * 1、参数是否有密文参数
 * 2、有密文则进行解密处理
 * 3、对解密的参数进行重装到HttpServletRequest中
 *
 * @author WuFengSheng on 2019/1/25.
 */
@Slf4j
public class ModifyRequestFileter extends OncePerRequestFilter {

    /**
     * Aes密钥key
     */
    @Value("${security.aes-key}")
    private String aesKey;

    /**
     * 签名key
     */
    @Value("${security.sign-key}")
    private String signKey;

    /**
     * @param request
     * @param response
     * @param filterChain
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String path = request.getServletPath();
        if (path != null && path.startsWith(BaseApi.getApiPathPrefix())) {
            ModifyRequestWrapper modifyRequestWrapper = new ModifyRequestWrapper(request);
            Map<String, String[]> parameterMap = new HashMap<>(modifyRequestWrapper.getParameterMap());
            String dataType = request.getParameter("dataType");
            String aesData = request.getParameter("aesData");

            boolean ifStatus = (dataType == null || aesData == null || aesData.length() == 0);
            if (ifStatus) {
                // 不进行解密
                log.info("不进行解密");
                filterChain.doFilter(request, response);
            } else {
                // 进行解密并进行验签与参数重装
                if (decryptAndVerifySign(request, response, parameterMap)) {
                    modifyRequestWrapper.setParameterMap(parameterMap);
                    //使用复写后的wrapper
                    filterChain.doFilter(modifyRequestWrapper, response);
                }
                return;
            }
        } else {
            filterChain.doFilter(request, response);
        }
    }

    /**
     * 解密并进行验签与参数重装
     *
     * @param request
     * @param response
     * @return
     */
    private synchronized boolean decryptAndVerifySign(HttpServletRequest request, HttpServletResponse response, Map<String, String[]> parameterMap) {
        String dataType = request.getParameter("dataType");
        if (dataType == null || dataType.length() == 0) {
            ResponseUtils.printWriterResponseVo(response, ResponseVo.fail("类型为空"));
            return false;
        }

        String signData = request.getParameter("signData");
        String aesData = request.getParameter("aesData");

        switch (dataType) {
            case "1":
                if (signData == null || signData.length() == 0) {
                    ResponseUtils.printWriterResponseVo(response, ResponseVo.fail("签名为空"));
                    return false;
                }
                if (aesData == null || aesData.length() == 0) {
                    ResponseUtils.printWriterResponseVo(response, ResponseVo.fail("密文为空"));
                    return false;
                }
                log.info("dataType={},signData={},aesData={}", dataType, signData, aesData);

                try {
                    // 报文内容AES解密
                    String decryptData = AesUtil.decryptAES(aesData, aesKey);
                    log.info("解密后数据={}", decryptData);
                    // 验证签名数据
                    Map<String, String> params = JSONObject.parseObject(decryptData, new TypeReference<Map<String, String>>() {
                    });

                    String mySignData = SignUtil.createLinkString(params, true);
                    mySignData += "&signKey=" + signKey;
                    log.info("mySignData={}", mySignData);

                    /**
                     * 小程序端中文加密MD5后有可能跟后台MD5不一样，小程序端MD5工具类需换成支持中文MD5工具类
                     */
                    mySignData = Md5Utils.hash(mySignData).toUpperCase();
                    log.info("mySignData={}", mySignData);
                    if (mySignData.contentEquals(signData)) {
                        /**
                         * 重装request
                         */
                        parameterMap.clear();
                        parameterMap.put("dataType", new String[]{dataType});
                        parameterMap.put("signData", new String[]{signData});
                        parameterMap.put("aesData", new String[]{aesData});

                        request.setCharacterEncoding("utf-8");
                        Iterator it = params.keySet().iterator();
                        while (it.hasNext()) {
                            String parameterName = it.next().toString();
                            if (!StringUtils.isEmpty(parameterName)) {
                                String name = "" + parameterName;
                                // URLDecoder.decode(params.get(parameterName), "UTF-8");
                                String value = "" + params.get(parameterName);
                                request.setAttribute(name, value);
                                // 重装请求参数
                                parameterMap.put(name, new String[]{value});
                            }
                        }
                        return true;
                    } else {
                        log.info("签名错误");
                        ResponseUtils.printWriterResponseVo(response, ResponseVo.fail("签名错误"));
                        return false;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    ResponseUtils.printWriterResponseVo(response, ResponseVo.fail("解密失败"));
                    return false;
                }
            default:
        }

        log.info("解密失败");
        ResponseUtils.printWriterResponseVo(response, ResponseVo.fail("解密失败"));
        return false;
    }
}
